﻿// <copyright file="Visual3D.cs" company="Axelerate Solutions S.A.">
// Copyright (c) Axelerate Solutions S.A.  All rights reserved.
// </copyright>

namespace Axelerate.Silverlight3D.Media.Media3D
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.ComponentModel;
    using System.Collections.Generic;
    using Axelerate.Silverlight3D.Controls;
    using System.Windows.Markup;
    
    [ContentPropertyAttribute("Children")]
    public abstract class Visual3D: FrameworkElement, INotifyPropertyChanged
    {

        #region Fields

        public static readonly DependencyProperty TransformProperty =
                    DependencyProperty.Register(
                          "Transform",
                          typeof(Transform3D),
                          typeof(Visual3D),
                          new PropertyMetadata(OnTransformPropertyChanged));

        public static readonly DependencyProperty ChildrenProperty =
                   DependencyProperty.Register(
                         "Children",
                         typeof(Visual3DCollection),
                         typeof(ModelVisual3D),
                         new PropertyMetadata(OnChildrenPropertyChanged));

        internal Viewport3D Viewport = null;

        protected Matrix3D localToWorldTransforms = Matrix3D.Identity;
        protected Matrix3D parentWorldTransforms = Matrix3D.Identity;
            
        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
        
        #region Properties

        /// <summary>
        /// Indicates if this model needs to be re-rendered
        /// </summary>
        internal bool IsDirty
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the color allowed to emit from the Material. This is a dependency property.
        /// </summary>
        public Transform3D Transform
        {
            get
            {

                return (Transform3D)this.GetValue(TransformProperty);
            }
            set
            {
                Transform3D OldValue = (Transform3D)this.GetValue(TransformProperty);
                if (OldValue != null)
                {
                    OldValue.PropertyChanged -= TransFormValue_PropertyChanged;
                }
                this.SetValue(TransformProperty, value);
                value.PropertyChanged += TransFormValue_PropertyChanged;
            }
        }

        /// <summary>
        /// Notify if the some property of the Transform was changed
        /// </summary>
        /// <param name="sender">Object that send the event</param>
        /// <param name="e">event arguments</param>
        void TransFormValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.NotifyPropertyChanged("Transform");
            this.UpdateWorldTransform();
        }
              
        /// <summary>
        /// Internal list of render entities (2D Shapes)
        /// </summary>
        internal abstract List<RenderEntity> innerRenderEntities { get; }

        public Visual3DCollection Children
        {
            get
            {

                return (Visual3DCollection)this.GetValue(ChildrenProperty);
            }
            private set
            {
                Visual3DCollection OldValue = (Visual3DCollection)this.GetValue(ChildrenProperty);
                if (OldValue != null)
                {
                    OldValue.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Childrenvalue_CollectionChanged);
                }
                this.SetValue(ChildrenProperty, value);
                value.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Childrenvalue_CollectionChanged);
            }
        }

        void Childrenvalue_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                foreach (Visual3D item in e.NewItems)
                {
                    item.Viewport = this.Viewport;
                    
                    item.CreateRenderEntities();
                    item.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            {
                foreach (Visual3D item in e.OldItems)
                {
                    item.Viewport = null;
                    item.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
            else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset)
            {
                foreach (Visual3D item in e.OldItems)
                {
                    item.Viewport = null;
                    item.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(item_PropertyChanged);
                }
            }
            this.NotifyPropertyChanged("Children");
        }

        void item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.NotifyPropertyChanged("Children");
        }

        
        #endregion

        #region Constructor
        public Visual3D()
            : base()
        {
            this.Children = new Visual3DCollection();
        }
        #endregion

        #region Methods

        /**************** */
        /// <summary>
        /// Update the Visual3D Childs tranforms with the transform associated to this Visual3D
        /// </summary>
        internal void SetParentWorldTransform(Matrix3D transform)
        {
            //TODO:Only set if they are different? Is this more efficient in
            //the long run - see usage pattern for this function call.
            this.parentWorldTransforms = transform;
            this.UpdateWorldTransform();
        }

        /// <summary>
        /// Returns the LocalToWorld transform for the model which is associated
        /// with the visual
        /// </summary>
        /// <param name="visual"></param>
        /// <returns></returns>
        internal Matrix3D GetLocalToWorldTransform()
        {
            //TODO: Will this only be called when the visual is associated with
            //      the model? Verify.
            return this.localToWorldTransforms;
        }

        /*
         * Local Transform - this is just for the model
         * LocalToWorldTransform -> depends on the visual the object is associated with
         * ParentWorldTransform -> depends on the visual the object is associated with
        */
        internal virtual void UpdateWorldTransform()
        {
            //TODO: What if the user set the local transform to NULL?
            //Need to investigate what WPF does in this scenario.

            
            //TODO: Verify always have a parent transform when have local transform?
            this.localToWorldTransforms  = this.Transform.Value * this.parentWorldTransforms;
            if (this.Children != null)
            {
                foreach (Visual3D childVisual in this.Children)
                {
                   childVisual.SetParentWorldTransform(this.GetLocalToWorldTransform()); 
                }
            }
            

            //Make sure we are re-rendered, since the transform has changed
            this.IsDirty = true;
        }
        /********************/



        /// <summary>
        /// Notify if a propertity changed
        /// </summary>
        /// <param name="propertyName">Name of the property changed</param>
        protected virtual void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
            this.IsDirty = true;
        }

        /// <summary>
        /// Raise the Transform changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnTransformPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Visual3D)d).NotifyPropertyChanged("Transform");
            ((Visual3D)d).UpdateWorldTransform();
        }

        /// <summary>
        /// Raise the Children changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnChildrenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((Visual3D)d).CreateRenderEntities();
            ((Visual3D)d).NotifyPropertyChanged("Content");
        }

        internal abstract void CreateRenderEntities();

        internal abstract void Render(Viewport3D Viewport, List<RenderEntity> entitiesToRender, List<ModelVisual3D> renderedVisuals);

        /// <summary>
        /// Returns a transform that can be used to transform coordinates from this Visual3D object to the specified Visual ancestor of the object.
        /// </summary>
        /// <param name="visual">The Visual to which the coordinates are transformed. In this moment only support the ViewPort3D as visual</param>
        /// <returns>A GeneralTransform3DTo2D object or a null reference (Nothing in Visual Basic), if the transform cannot be created.</returns>
        public GeneralTransform3DTo2D TransformToAncestor(FrameworkElement visual)
        {
            //TODO: Fully support - for now just support Viewport3D
            Viewport3D viewport = visual as Viewport3D;

            if (viewport == null)
            {
                return null;
            }

            if (viewport.Camera == null)
            {
                return null;
            }

            /*
            //The viewport should be the same viewport that the model visual
            //is assocaiated with
            if (viewport != this.Viewport)
            {
                return null;
            }
            */

            ModelVisual3D modelVisual = this as ModelVisual3D;
            if (modelVisual == null)
            {
                return null;
            }

            Matrix3D matrix = modelVisual.GetLocalToWorldTransform();
            matrix.Append(viewport.Camera.WorldToScreenMatrix);
            return new GeneralTransform3DTo2D(matrix);
        }
        
        #endregion

    }
}
